﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Data;
using System.Data.Common;
using System.Diagnostics;
using Microsoft.Practices.Unity;
using Vit.Common;

namespace Vit.Data
{
    public class DbCommandWrapper : DbCommand
    {
        private DbCommand wrappedCommand;
        private DbConnectionWrapper wrappedConnection;
        private List<string> affectedEntityNames;

        public DbCommandWrapper(DbCommand wrappedCommand, DbConnectionWrapper connection)
        {
            this.wrappedCommand = wrappedCommand;
            this.wrappedConnection = connection;
            affectedEntityNames = new List<string>();
        }

        public override void Cancel()
        {
            this.wrappedCommand.Cancel();
        }

        public override string CommandText
        {
            get
            {
                return this.wrappedCommand.CommandText;
            }
            set
            {
                this.wrappedCommand.CommandText = value;
            }
        }

        public override int CommandTimeout
        {
            get
            {
                return this.wrappedCommand.CommandTimeout;
            }
            set
            {
                this.wrappedCommand.CommandTimeout = value;
            }
        }

        public override CommandType CommandType
        {
            get
            {
                return this.wrappedCommand.CommandType;
            }
            set
            {
                this.wrappedCommand.CommandType = value;
            }
        }

        protected override DbParameter CreateDbParameter()
        {
            return this.wrappedCommand.CreateParameter();
        }

        protected override DbConnection DbConnection
        {
            get
            {
                return this.wrappedCommand.Connection;
            }
            set
            {
                if (value is DbConnectionWrapper)
                    this.wrappedCommand.Connection = value;
                else
                {
                    DbConnectionWrapper conn = new DbConnectionWrapper(value);
                    this.wrappedCommand.Connection = conn;
                }
            }
        }

        protected override DbParameterCollection DbParameterCollection
        {
            get { return this.wrappedCommand.Parameters; }
        }

        protected override DbTransaction DbTransaction
        {
            get
            {
                return this.wrappedCommand.Transaction;
            }
            set
            {
                if (value == null)
                    return;
                if (value is DbTransactionWrapper)
                {
                    IEnumerable<string> names = GetAffectedEntityNames(this.CommandText);
                    this.affectedEntityNames.AddRange(names);
                    ((DbTransactionWrapper)value).AddAffectedEntities(names);
                    this.wrappedCommand.Transaction = ((DbTransactionWrapper)value).DbTransaction;
                }
                else
                    throw new NotSupportedException("the connection of transaction is not wrapped");
            }
        }

        public override bool DesignTimeVisible
        {
            get
            {
                return false;
            }
            set
            {
            }
        }

        protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior)
        {
            CommandExecutionEventArgs e = new CommandExecutionEventArgs(this, "ExecuteReader");
            e.Status = CommandExecutionStatus.Executing;
            DbDataReader reader = null;
            bool cancel = false;
            foreach (IDbConnectionVisitor visitor in DbConnectionVisitorManager.Visitors)
            {
                if (visitor.IsVisiting(wrappedConnection))
                {
                    visitor.OnCommandExecuting(this, e);
                    if (e.Cancel)
                        cancel = e.Cancel;
                    reader = e.Result as DbDataReader;
                }
            }

            if (!e.Cancel)
            {
                try
                {
                    Stopwatch sw = new Stopwatch();
                    sw.Start();
                    reader = this.wrappedCommand.ExecuteReader(behavior);
                    sw.Stop();
                    e.Result = reader;
                    e.Status = CommandExecutionStatus.Finished;
                    e.Duration = sw.Elapsed;

                    foreach (IDbConnectionVisitor visitor in DbConnectionVisitorManager.Visitors)
                    {
                        if (visitor.IsVisiting(wrappedConnection))
                            visitor.OnCommandExecuted(this, e);
                    }

                    return reader;
                }
                catch (Exception ex)
                {
                    e.Result = ex;
                    e.Status = CommandExecutionStatus.Failed;
                    foreach (IDbConnectionVisitor visitor in DbConnectionVisitorManager.Visitors)
                    {
                        if (visitor.IsVisiting(wrappedConnection))
                            visitor.OnCommandFailed(this, e);
                    }

                    throw;
                }
            }
            return reader;
        }

        public override int ExecuteNonQuery()
        {
            CommandExecutionEventArgs e = new CommandExecutionEventArgs(this, "ExecuteNonQuery");
            e.Status = CommandExecutionStatus.Executing;
            bool cancel = false;
            foreach (IDbConnectionVisitor visitor in DbConnectionVisitorManager.Visitors)
            {
                if (visitor.IsVisiting(wrappedConnection))
                {
                    visitor.OnCommandExecuting(this, e);
                    if (e.Cancel)
                        cancel = e.Cancel;
                }
            }
            if (!cancel)
            {
                try
                {
                    Stopwatch sw = new Stopwatch();
                    sw.Start();
                    int result = wrappedCommand.ExecuteNonQuery();
                    sw.Stop();
                    e.Result = result;
                    e.Duration = sw.Elapsed;
                    e.Status = CommandExecutionStatus.Finished;
                    foreach (IDbConnectionVisitor visitor in DbConnectionVisitorManager.Visitors)
                    {
                        if (visitor.IsVisiting(wrappedConnection))
                            visitor.OnCommandExecuted(this, e);
                    }
                    return result;
                }
                catch (Exception ex)
                {
                    e.Result = ex;
                    e.Status = CommandExecutionStatus.Failed;
                    foreach (IDbConnectionVisitor visitor in DbConnectionVisitorManager.Visitors)
                    {
                        if (visitor.IsVisiting(wrappedConnection))
                            visitor.OnCommandFailed(this, e);
                    }
                    throw;
                }
            }
            return 0;
        }

        public override object ExecuteScalar()
        {
            CommandExecutionEventArgs e = new CommandExecutionEventArgs(this, "ExecuteScalar");
            e.Status = CommandExecutionStatus.Executing;
            bool cancel = false;
            foreach (IDbConnectionVisitor visitor in DbConnectionVisitorManager.Visitors)
            {
                if (visitor.IsVisiting(wrappedConnection))
                {
                    visitor.OnCommandExecuting(this, e);
                    if (e.Cancel)
                        cancel = e.Cancel;
                }
            }
            if (!cancel)
            {
                try
                {
                    Stopwatch sw = new Stopwatch();
                    sw.Start();
                    object result = wrappedCommand.ExecuteScalar();
                    sw.Stop();
                    e.Result = result;
                    e.Duration = sw.Elapsed;
                    e.Status = CommandExecutionStatus.Finished;
                    foreach (IDbConnectionVisitor visitor in DbConnectionVisitorManager.Visitors)
                    {
                        if (visitor.IsVisiting(wrappedConnection))
                            visitor.OnCommandExecuted(this, e);
                    }
                    return result;
                }
                catch (Exception ex)
                {
                    e.Result = ex;
                    e.Status = CommandExecutionStatus.Failed;
                    foreach (IDbConnectionVisitor visitor in DbConnectionVisitorManager.Visitors)
                    {
                        if (visitor.IsVisiting(wrappedConnection))
                            visitor.OnCommandFailed(this, e);
                    }
                    throw;
                }
            }
            return null;
        }

        public override void Prepare()
        {
            this.wrappedCommand.Prepare();
        }

        public override UpdateRowSource UpdatedRowSource
        {
            get
            {
                return this.wrappedCommand.UpdatedRowSource;
            }
            set
            {
                this.wrappedCommand.UpdatedRowSource = value;
            }
        }

        public string ToUniqueName()
        {
            StringBuilder sb = new StringBuilder();
            sb.Append(this.wrappedCommand.CommandText);

            foreach (DbParameter par in this.wrappedCommand.Parameters)
            {
                sb.AppendFormat(
                    System.Globalization.CultureInfo.InvariantCulture,
                    "{0} (dbtype={1}, size={4}, direction={2}) = {3}",
                    par.ParameterName,
                    par.DbType,
                    par.Direction,
                    par.Value as string ?? "null",
                    par.Size);
            }

            return sb.ToString();
        }

        internal static IEnumerable<string> GetAffectedEntityNames(string sql)
        {
            string pattern = @"((INSERT\s+(INTO)?)|(UPDATE)|(DELETE\s+(FROM)?))\s+(((\[\w+\])|(\w+))\.)?((\[(?<name>\w+)\])|((?<name>\w+)))";
            MatchCollection matches = Regex.Matches(sql, pattern, RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.IgnoreCase);
            foreach (Match m in matches)
            {
                string entity = m.Groups["name"].Value;
                yield return entity;
            }
        }

        public List<string> AffectedEntities
        {
            get { return affectedEntityNames; }
        }
    }
}
